package com.limegroup.gnutella.gui.tables; import java.awt.Graphics; import java.awt.Image; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragSource; import java.awt.dnd.MouseDragGestureRecognizer; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.List; import javax.swing.CellRendererPane; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JTree; import javax.swing.SwingConstants; import javax.swing.ToolTipManager; import com.limegroup.gnutella.gui.IconManager; import com.limegroup.gnutella.util.CommonUtils; /** * Manages installation of the drag aspect of a LimeJTable. */ public final class DragManager { /** * The pane to use for drawing the Drag image. */ private static final CellRendererPane PANE = new CellRendererPane(); /** * The maximum width of the drag image. */ private static final int IMAGE_WIDTH = 300; /** * The height of each row in the drag image. */ private static final int IMAGE_ROW_HEIGHT = 16; /** * Empty constructor -- all management is static. */ private DragManager() {} /** * Installs drag recognition for the specified JTree. */ public static void install(JTree tree) { // Construct the drag recognizer. try { DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer( tree, DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK, DragListener.instance()); } catch(Throwable ignored) {} } /** * Installs drag recognition for the specified LimeJTable. * * This does a bit of voodoo to make everything work: * A drag gesture recognizer uses a MouseListener & a MouseMotionListener * to recognize drag events. Unfortunately, list selection also * uses MouseListeners & MouseMotionListeners, and these two listeners * can become at odds with each other -- the drag recognizer starting * a drag just as the list selection changes... * In order to resolve everything, we need to make careful use of * 'consume()' and 'isConsumed()' in MouseEvent. On Windows, the * listeners for selection correctly check for isConsumed before * processing. This is not done on OSX. To work on all platforms, * we install a proxy Mouse[Motion]Listener that discards the appropriate * events if they were consumed. * Further complicating things, the DragGestureRecognizers are platform * dependent, using various key combinations to initiate the drag with * different default actions (move / copy / link). Even worse, these * platform dependent D&D listeners do not consume the mouse event. * To work around this, we make use of the fact that the default * drag gesture recognizer extends MouseDragGestureRecognizer, which is * both a MouseListener & MouseMotionListener, and then wrap the platform * dependent listener around our own TableDragRecnogitionWrapper, which * correctly consumes mouse events, preventing the selection code from * changing the selection. * * A bit of estoric wierdness is added with tooltips, in that they will NPE * if they don't receive all events, all the time. To work around that, * we first unregister the component for tooltips & then later reregister * it. * * The order of listeners must be: * - The D&D listeners, a TableDragRecognitionWrapper. * - Pre-existing listeners, checked for mouse event consumption. * - Tooltip listeners, not checked for mouse event consumption. * - Any listeners added later. * */ public static void install(LimeJTable table) { // First unregister tooltips -- we don't want to proxy tooltip events. ToolTipManager.sharedInstance().unregisterComponent(table); // Get a copy of the old MouseListener & MouseMotionListeners, so we // can later proxy them through a MouseEventConsumptionChecker. MouseListener[] oldMouseListeners = (MouseListener[])table.getListeners(MouseListener.class); MouseMotionListener[] oldMouseMotionListeners = (MouseMotionListener[])table.getListeners(MouseMotionListener.class); // Remove them, so the DragGestureRecognizer listeners can be installed // as the first listeners. for(int i = 0; i < oldMouseListeners.length; i++) table.removeMouseListener(oldMouseListeners[i]); for(int i = 0; i < oldMouseMotionListeners.length; i++) table.removeMouseMotionListener(oldMouseMotionListeners[i]); // Construct the drag recognizer. // We want to use a platform-dependent GestureRecognizer, // so that the fireDragGestureRecognized event // can set the correct actions based on user input, // but we also want to use our listeners // for input, so that can consume the event appropriately // and only make dragging possible if something was selected. MouseDragGestureRecognizer recognizer; try { recognizer = (MouseDragGestureRecognizer) DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer( table, DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK, DragListener.instance()); } catch(Throwable failed) { // If it can't load DnD, then restore & exit. for(int i = 0; i < oldMouseListeners.length; i++) table.addMouseListener(oldMouseListeners[i]); for(int i = 0; i < oldMouseMotionListeners.length; i++) table.addMouseMotionListener(oldMouseMotionListeners[i]); ToolTipManager.sharedInstance().registerComponent(table); return; } // Wrap the recognizer around another recognizer that will also // check for selection. table.removeMouseListener(recognizer); table.removeMouseMotionListener(recognizer); TableDragRecognitionWrapper inputListener = new TableDragRecognitionWrapper(table, recognizer); table.addMouseListener(inputListener); table.addMouseMotionListener(inputListener); DragSource.getDefaultDragSource().addDragSourceListener(inputListener); // Re-add the mouse & mouse motion listeners, proxied by the // event-consumption checker. for(int i = 0; i < oldMouseListeners.length; i++) table.addMouseListener(MouseEventConsumptionChecker.proxy(oldMouseListeners[i])); for(int i = 0; i < oldMouseMotionListeners.length; i++) table.addMouseMotionListener(MouseEventConsumptionChecker.proxy(oldMouseMotionListeners[i])); table.setMouseEventsProxied(true); // Then re-register tooltips. ToolTipManager.sharedInstance().registerComponent(table); } /** * Creates an image for the given transferable. */ public static Image createDragImage(Transferable t) { if(!t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) return null; List l = null; try { l = (List)t.getTransferData(DataFlavor.javaFileListFlavor); } catch(UnsupportedFlavorException ufe) { return null; } catch(IOException ioe) { return null; } // if no data, there's nothing to draw. if(l.size() == 0) return null; int height = IMAGE_ROW_HEIGHT * l.size(); BufferedImage buffer = new BufferedImage( IMAGE_WIDTH, height, BufferedImage.TYPE_INT_ARGB); Graphics g = buffer.getGraphics(); JLabel label = new JLabel(); label.setVerticalAlignment(SwingConstants.TOP); label.setOpaque(false); int y = 0; for(Iterator i = l.iterator(); i.hasNext(); ) { File f = (File)i.next(); Icon icon = IconManager.instance().getIconForFile(f); label.setIcon(icon); label.setText(f.getName()); PANE.paintComponent(g, label, null, 0, y, IMAGE_WIDTH, height - y); y += IMAGE_ROW_HEIGHT; } g.dispose(); return buffer; } }